﻿using System;
using System.Collections.Generic;

namespace Vosvek.ProjectRogue
{

    // TODO: Add Inspector/Editor magic!

    [Serializable]
    public class Inventory
    {
        public List<ItemStack> Items = new List<ItemStack>();

        public double CachedTotalWeight { get; private set; } = 0.0d;
        public long CachedCoinValue { get; private set; } = 0L;
        public bool IsEmpty { get { return Items.Count < 1; } }
        public int TotalStacks { get { return Items.Count; } }

        /// <summary>
        /// The same as <see cref="PutItem(Item, int)"/>, but using an 
        /// ItemStack.
        /// </summary>
        public void PutItem(ItemStack itemStack)
        {
            PutItem(itemStack.Item, itemStack.Quantity);
        }

        /// <summary>
        /// Adds the item and amount to the inventory. If the amount is less 
        /// than one, this function does nothing.
        /// </summary>
        /// <param name="item">The item to add.</param>
        /// <param name="amount">The amount of the item to add.</param>
        public void PutItem(Item item, int amount = 1)
        {
            // EARLY EXIT: If not adding an item, return.
            if (amount < 1) return;

            for (int i = Items.Count - 1; i >= 0; i--)
            {
                // EARLY EXIT: If the item exists in the list, increase the 
                // amount, and return.
                if (Items[i].Item.Equals(item))
                {
                    Items[i].Quantity += amount;
                    return;
                }
            }

            // The item does not exist, so add it.
            Items.Add(new ItemStack(item, amount));

            // Adjust coin value and total weight.
            CachedTotalWeight += item.Weight * amount;
            CachedCoinValue += item.CoinValue * amount;
        }

        /// <summary>
        /// The same as <see cref="TakeItem(Item, int)"/>, but using an 
        /// ItemStack.
        /// </summary>
        public int TakeItem(ItemStack itemStack)
        {
            return TakeItem(itemStack.Item, itemStack.Quantity);
        }

        /// <summary>
        /// Tries to take the specified amount of Item from the inventory. It 
        /// returns the total amount taken. So if you are trying to take more 
        /// than there exists in the inventory, you know how much you actually 
        /// took.
        /// </summary>
        /// <param name="item">The item to take.</param>
        /// <param name="amount">The amount to take.</param>
        /// <returns>The amount of items taken.</returns>
        public int TakeItem(Item item, int amount = 1)
        {
            // EARLY EXIT: If not taking an item, return.
            if (amount < 1) return 0;

            for (int i = Items.Count - 1; i >= 0; i--)
            {
                // EARLY EXIT: If item exists, 
                if (Items[i].Item.Equals(item))
                {
                    // ... calculate the total amount of items remove.
                    int amountRemoved = Items[i].Quantity - amount;

                    if (amountRemoved < 0)
                    {
                        amountRemoved = Items[i].Quantity;
                    }

                    // Adjust coin value and total weight.
                    CachedTotalWeight -= item.Weight * amountRemoved;
                    CachedCoinValue -= item.CoinValue * amountRemoved;

                    // If the item stack no longer has any items in the stack, 
                    // remove the item stack.
                    if (Items[i].Quantity < 1)
                    {
                        Items.RemoveAt(i);
                    }

                    return amountRemoved;
                }
            }

            // Item not found, so return 0.
            return 0;
        }

        /// <summary>
        /// The same as <see cref="ContainsItem(Item, int)"/>, but using an 
        /// ItemStack.
        /// </summary>
        public bool ContainsItem(ItemStack itemStack)
        {
            return ContainsItem(itemStack.Item, itemStack.Quantity);
        }

        /// <summary>
        /// Gets whether the Item of the specified amount exists in the 
        /// inventory. So, if the inventory contains less than the specified 
        /// amount, it returns false. On the other hand, if the inventory 
        /// contains the item with an amount equal to or greater than the amount
        /// specified, it returns true.
        /// </summary>
        /// <param name="item">The item to find.</param>
        /// <param name="amount">The amount to expect.</param>
        /// <returns>True if the item and specified amount are in the inventory.
        /// </returns>
        public bool ContainsItem(Item item, int amount = 1)
        {
            // EARLY EXIT: If the amount is less than one, just return false.
            if (amount < 1) return false;

            for (int i = Items.Count - 1; i >= 0; i--)
            {
                // EARLY EXIT: If found the item, and the amount is greater than 
                // or equal to the amount specified, return true. Else, return
                // false.
                if (Items[i].Item.Equals(item))
                {
                    if (Items[i].Quantity >= amount)
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
            }

            // Item not found.
            return false;
        }

        /// <summary>
        /// WARNING! Costly operation. Squashes the inventory to remove 
        /// duplicates, and combine duplicated amount values into single stacks.
        /// It then removes empty values, and recalculates the total weight and 
        /// coin values.
        /// </summary>
        public void SquashDuplicates()
        {
            List<ItemStack> squashedList = new List<ItemStack>();

            for (int i = Items.Count - 1; i >= 0; i--)
            {
                for (int s = squashedList.Count - 1; s >= 0; s--)
                {
                    // If has seen this item, add the AMOUNT to the squashed list.
                    if (Items[i].Item.Equals(squashedList[s].Item))
                    {
                        squashedList[s].Quantity += Items[i].Quantity;
                    }
                    // Otherwise, add the ItemStack to the squashed list.
                    else
                    {
                        squashedList.Add(Items[i]);
                    }
                }
            }

            // Set Items the squashed list.
            Items = squashedList;

            // Remove empty values.
            RemoveEmptyValues();

            // Recalculate total weight and coin values.
            RecalculateTotalWeight();
            RecalculateTotalCoinValue();
        }

        /// <summary>
        /// Calculates the inventory's total weight, setting the cached total 
        /// weight variable, and returning the resulting value.
        /// </summary>
        /// <returns>The total weight.</returns>
        public double RecalculateTotalWeight()
        {
            double result = 0.0d;

            for (int i = Items.Count - 1; i >= 0; i--)
            {
                result += Items[i].Item.Weight * Items[i].Quantity;
            }

            // Cache calculated value.
            CachedTotalWeight = result;

            return result;
        }

        /// <summary>
        /// Calculates the inventory's total coin value, setting the cached 
        /// total coin value variable, and returning the resulting value.
        /// </summary>
        /// <returns>The total coin value.</returns>
        public long RecalculateTotalCoinValue()
        {
            long result = 0L;

            for (int i = Items.Count - 1; i >= 0; i--)
            {
                result += Items[i].Item.CoinValue * Items[i].Quantity;
            }

            // Cache calculated value.
            CachedCoinValue = result;

            return result;
        }

        /// <summary>
        /// Removes all item stacks with an amount less than 0.
        /// </summary>
        public void RemoveEmptyValues()
        {
            for (int i = Items.Count - 1; i >= 0; i--)
            {
                if (Items[i].Quantity <= 0)
                {
                    Items.RemoveAt(i);
                }
            }
        }
    }

}
